WebRTC源码分析 您所在的位置:网站首页 webrtc track WebRTC源码分析

WebRTC源码分析

#WebRTC源码分析| 来源: 网络整理| 查看: 265

目录 1. 引言2. 视频轨道的创建和添加2.1 视频源的创建2.1.1 创建视频采集2.1.1.1 视频采集UML2.1.1.2 采集模块的内部数据流 2.1.2 创建视频源2.1.3 创建视频轨源 2.2 视频轨的创建2.2.1 PeerConnection::CreateVideoTrack2.2.2 视频轨的继承树 2.3 视频数据的流动2.4 添加视频轨到PeerConnection 3 总结

1. 引言

创建完PeerConnectionFactory 和 PeerConnection这两个API层的操盘对象之后,紧接着需要初始化本地的媒体,也即创建本地的音频轨、视频轨、数据通道,并将这些本地的媒体轨道添加到PeerConnection对象中。如图中红色标注所示。

本文将详细描述上述视频轨道的创建细节 以及 轨道被添加到PeerConnection中的存储情况。

在这里插入图片描述

2. 视频轨道的创建和添加 rtc::scoped_refptr video_device = CapturerTrackSource::Create(); if (video_device) { rtc::scoped_refptr video_track_( peer_connection_factory_->CreateVideoTrack(kVideoLabel, video_device)); main_wnd_->StartLocalRenderer(video_track_); result_or_error = peer_connection_->AddTrack(video_track_, {kStreamId}); if (!result_or_error.ok()) { RTC_LOG(LS_ERROR) // 1. 预设视频参数 const size_t kWidth = 640; const size_t kHeight = 480; const size_t kFps = 30; std::unique_ptr capturer; // 2. 获取视频设备信息对象info,并获取机器上视频设备个数 std::unique_ptr info( webrtc::VideoCaptureFactory::CreateDeviceInfo()); if (!info) { return nullptr; } int num_devices = info->NumberOfDevices(); // 3. 枚举音频设备,查找支持预设视频参数的设备,创建对应的视频源,然后创建对应的轨道源 for (int i = 0; i return new rtc::RefCountedObject( std::move(capturer)); } } return nullptr; }

从上创建视频源的代码可以知道如下几点:

通过设备信息类VideoCaptureModule::DeviceInfo获取音视频设备个数需要根据预设的视频参数宽高,帧率去创建视频源对象VcmCapturer,一旦音频源对象VcmCapturer创建成功,则认为找到了底层适用于该预设视频参数的音频采集VideoCapture设备。以创建的视频源对象VcmCapturer为入参,构建视频轨源对象CapturerTrackSource。VcmCapturer成为CapturerTrackSource的私有成员。

CapturerTrackSource、VcmCapturer、VideoCapture对象的继承树,以及这三者的关系如下UML类图所示: 在这里插入图片描述

2.1.1 创建视频采集

视频采集模块是数据流水线的起始点,负责从视频源采集原始视频帧,推送给流水线的下一站:可以是本地渲染模块进行本地回显,也可以是编码模块进行数据编码压缩。

视频源可以是摄像头,也可以是桌面、窗口抓屏(远程桌面,基于视频流的电子白板等应用),甚至可以是磁盘上的视频文件,图片文件。WebRTC中提供了基于摄像头的视频采集框架,是本文要讨论的重点。当然WebRTC也提供了桌面,窗口抓屏框架,这套框架对外所提供的接口与基于摄像头的采集接口有所不同。整个视频流水线建立是以摄像头采集接口为基础的,从而导致这么个问题:当需要将抓屏数据当做视频源往外推送时,需要使用适配器模式来实现一套基于摄像头的视频采集接口。

视频采集模块是平台相关的模块,MacOS/IOS一般使用AVFoundation框架或者QuikTime框架,Linux平台一般使用V4L2库,Android上一般使用Camera1或者Camera2框架,Windows平台则使用DS(DirectShow)或者是MF(MediaFoundation)。由于WebRTC是个非常活跃的工程,代码架构一直在不停的变动之中,比如2019年4月份的代码还有VideoCaptureMF的代码,并且还注释着Vista及以上的版本建议使用MediaFoundation采集框架,而2019年11月份的代码MediaFoundation相关的代码已经被移除。再比如MacOs/IOS,Android的相关代码已经被移动到sdk/objc和sdk/android目录下。本文以modules/video_capture下的代码来做阐述,平台无关的代码在该直接目录下,平台相关的实现在modules/video_capture/windows,modules/video_capture/linux目录下,如图所示: 在这里插入图片描述

2.1.1.1 视频采集UML

在这里插入图片描述 DeviceInfo接口提供了设备枚举相关功能,其平台相关子类实例以组合的形式提供给VideoCapture。

枚举设备个数,获取某个设备名称。枚举某个设备所支持的所有能力(VideoCaptureCapability: 分辨率,最大帧率,颜色空间,是否逐行扫描)获取某个设备的所有能力中与外部设置的能力最匹配的那个能力。

VideoCaptureModule视频采集模块的虚基类,它定义一系列视频采集的通用接口函数:

Start/StopCapture用来开始/结束视频采集(平台相关);CaptureStarted用来判断当前capture运行状态(平台相关);Register/DeCaptureDataCallback用来注册/注销数据回调模块(平台无关);Set/GetApplyRotation用来设置视频旋转角度(平台无关)。

VideoCaptureImpl类是VideoCaptureModule的实现子类。做了3个事:

声明静态Ctreate方法,用于创建平台相关的VideoCaptureImpl子类,在Windows平台上为VideoCaptureDS,在Linux平台上实现的子类是VideoCaptureV4L2。该方法一处声明,多处实现,在相应平台编译时,只会加载对应平台的实现代码;平台相关的接口,留待平台相关的子类中实现,主要是开始/结束视频采集;实现平台无关的接口:注册视频数据回调,应用视频旋转相关函数。其中注册数据回调将一个实现了VideoSinkInterface接口的对象赋予VideoCaptureImpl::_dataCallBack成员。当采集模块得到一帧视频数据,就可以通过该对象的OnFrame()方法推送出来。 2.1.1.2 采集模块的内部数据流

在这里插入图片描述

以VideoCaptureDS为例,平台相关的采集模块采集到一帧视频后,平台相关的函数ProcessCapturedFrame()方法进行处理。ProcessCapturedFrame()将视频帧直接传递给VideoCaptureImpl::IncomingFrame()方法VideoCaptureImpl::IncomingFrame()方法将对视频帧按需求进行旋转,并利用libyuv库转换成I420类型,再给视频帧加上NTP时间戳。经过上述处理后,IncomingFrame()将视频帧进一步传递给VideoCaptureImpl::DeliverCapturedFrame()VideoCaptureImpl::DeliverCapturedFrame()将调用VideoSinkInterface::OnFrame(),将视频帧传递给回调对象_dataCallBack,即数据的下一站,从而将视频帧推送出采集模块。 2.1.2 创建视频源

VcmCapturer是创建的视频源对象,虽然从名字上来看像是视频采集类,但实质上它实现了VideoSourceInterface接口。我们认为其是一个视频源。 在这里插入图片描述 VideoCaptureModule作为数据源头组合到视频源对象VcmCapturer中,同时VcmCapturer又实现了VideoSinkInterface接口,可以把自己注册到VideoCaptureModule中。从而实现了视频帧从VideoCaptureModule->VideoSource的流动。

VcmCapturer在构造成功时就会启动VideoCaptureModule进行视频采集。

视频源VcmCapturer持有一个非常重要的成员VideoBroadcaster对象,该对象的UML类图如下。 在这里插入图片描述 一方面VideoBroadcaster实现了VideoSinkInterface接口,成为一个Sink,这样VideoSource得到采集模块的视频帧后,首先会流入到内部的VideoBroadcaster成员对象,而非直接从VideoSource流出;另一方面VideoSource和VideoBroadcaster都实现了VideoSourceInterface接口,对外VideoSource作为视频源存在,向数据流下一站提供注册方法AddOrUpdateSink();该方法内部调用VideoBroadcaster的AddOrUpdateSink(),从而将数据流下一站VideoSink注册到VideoBroadcaster,存入成员std::vector sinks_中。到此,应该不难想到VideoBroadcaster既有了数据流入,还知道数据的下一站(可能多个),那么VideoBroadcaster::OnFrame()中就可以通过循环调用下一站的OnFrame方法将视频帧广播出去。

为什么要如此设计?因为,在WebRTC 1.0的官方规范中说明了一个视频源是可以被多个视频轨共用的。通过上述方式可以实现共用的概念。

2.1.3 创建视频轨源

在这里插入图片描述 VideoTrackSource没有实现VideoSinkInterface接口,因此,实质上视频数据是不会流入到VideoTrackSource中的,但其组合了VideoSource对象,并且实现了VideoSourceInterface接口,添加到VideoTrackSource中的VideoSink会被添加到VideoSource,然后进一步添加到VideoBroadcast中。对外部来说,VideoTrackSource就是视频源。

VideoTrackSource另外实现了视频源状态相关的接口,以及状态通告相关的接口NotifierInterface,用于向更高一层(VideoTrack)通告视频源的状态。

2.2 视频轨的创建 2.2.1 PeerConnection::CreateVideoTrack rtc::scoped_refptr video_track_( peer_connection_factory_->CreateVideoTrack(kVideoLabel, video_device)); rtc::scoped_refptr PeerConnectionFactory::CreateVideoTrack( const std::string& id, VideoTrackSourceInterface* source) { RTC_DCHECK(signaling_thread_->IsCurrent()); rtc::scoped_refptr track( VideoTrack::Create(id, source, worker_thread_)); return VideoTrackProxy::Create(signaling_thread_, worker_thread_, track); } rtc::scoped_refptr VideoTrack::Create( const std::string& id, VideoTrackSourceInterface* source, rtc::Thread* worker_thread) { rtc::RefCountedObject* track = new rtc::RefCountedObject(id, source, worker_thread); return track; } VideoTrack::VideoTrack(const std::string& label, VideoTrackSourceInterface* video_source, rtc::Thread* worker_thread) : MediaStreamTrack(label), worker_thread_(worker_thread), video_source_(video_source), content_hint_(ContentHint::kNone) { video_source_->RegisterObserver(this); }

由上视频轨创建过程可知,

我们最终创建的实体视频轨是VideoTrack这个类的对象,但是向应用层返回的是相对应的代理对象VideoTrackProxy,这是WebRTC防止线程乱入的常规操作,原因不再多赘述。另外由代理对象的构建方式可知,VideoTrack的部分方法需要在信令线程执行,部分方法需要在工作者线程执行。VideoTrack成为源的观察者,可以获知源的状态改变。 2.2.2 视频轨的继承树

如下是VideoTrack类的UML类图。 在这里插入图片描述 从上VideoTrack类的UML类图我们可以得知如下几个结论:

VideoTrack一方面要实现ObserverInterface接口,让自己可以成为视频源的观察者,让自己的状态可以随着源的状态进行同步改变;另一方面又要实现NotifierInterface接口,让自己成为一个通知者,当自己的状态改变时,能够通知那些观察自己状态变化的对象。如同VideoTrackSource一般,VideoTrack也没有实现VideoSinkInterface接口,因此,视频数据也不会流入到VideoTrack中,但其组合了VideoTrackSource,并且间接实现了VideoSourceInterface接口。想要从VideoTrack中获取视频流的站点,只要实现VideoSinkInterface接口,通过VideoTrack的AddOrUpdateSink()注册进来即可,因为该VideoSink会经过VideoTrackSource->VideoSource->VideoBroadcaster,最终可以从VideoBroadcaster获得视频流。VideoTrack还实现了VideoTrackInterface接口,其中提供了一个重要的属性:ContentHint。这个属性告知编码器在码率降低时,应该如何应对:降低帧率?降低分辨率?对于桌面采集应用来说,我们应该设置该属性为kDetailed或者是kText,这样编码器编码该视频流的时候不会降低分辨率,量化参数qp值也不会设置的过大。我认为VideoTrack没有直接继承实现VideoSourceInterface接口,或者像AudioTrack那样直接提供AddOrUpdateSink/RemoveSink方法,而是继承了VideoSourceBase类,这样,往VideoTrack中的添加的VideoSink在VideoBroadcaster保存一份,也会在VideoTrack中保存一份。在VideoTrack保存这一份实质上没有什么作用,并且这样的继承关系跟AudioTrack的继承关系不对称,毫无美感而言。 2.3 视频数据的流动

纵观上述几个对象,我们可以得出如下的类图: 在这里插入图片描述 我们可以认为视频数据原始帧从VideoCapture流向了VideoSource,然后又流向了VideoTrackSource,然后流向VideoTrack。向VideoTrack注册的Sink又可以进一步获取到视频原始帧。

虽然比较细致地拆解了视频源为VideoTrackSource、VideoSource、VideoCapture,但从宏观概念上,我们要知晓:可以直接认为VideoTrackSource是视频源。数据从视频源流向视频轨。

视频轨既要从视频源获取视频数据,还要观察视频源地状态,从而同步更改自己地状态。

2.4 添加视频轨到PeerConnection

由于VideoTrack同AudioTrack一样,都实现了MediaStreamTrackInterface接口,对于PC而言,视频轨和音频轨并无实质区别,因为都是通过如下方法进行添加的。因此,在此处就不再赘述VideoTrack如何被添加?存储在PC的何处?是否将引发PC状态的改变? 等等,见上篇文章:WebRTC源码分析-呼叫建立过程之四(上)(创建并添加本地音频轨到PeerConnection)

RTCErrorOr AddTrack( rtc::scoped_refptr track, const std::vector& stream_ids) override; 3 总结

行文知此,大致对如何创建一个视频轨并添加到PC进行了一个较细致的分析。即便忘记了细节,那么如下要点是需要记住的

视频轨的实体对象是VideoTrack类,对于应用层返回的是其代理对象VideoTrackProxy,这是WebRTC为了防止线程乱入所作的常规操作。WebRTC中,媒体数据总是从Source流向Sink。某个对象要想从VideoTrack获取到视频帧,那么它需要实现VideoSinkInterface,并注册到VideoTrack中。实质上向VideoTrack注册的Sink会被传递到VideoTrack所持有的VideoSource的VideoBroadcast成员对象中,从那儿可以获得从VideoCapture传递来的视频帧。记住VideoTrack提供了ContentHint这样一个属性以及设置这个属性的接口。该属性描绘了视频帧的内容,这个会影响到编码器的降级策略(当网络带宽资源 && CPU资源受限时,无法按照既定的编码参数进行编码输出,那么就需要降级编码)是保持帧率,还是保持分辨率。当我们做远程桌面、电子白板这种应用时,我们认为内容清晰度比流畅性更重要,此时就需要保持分辨率,降低一些帧率。那么将VideoTrack的ContentHint属性设置为kDetailed or kText,那么就暗示了编码器保持分辨率;当我们做音视频会话时,我们需要保证音视频的流畅性、连贯性,因此,一般会采取保帧率,降分辨率的策略,那么可以将VideoTrack的ContentHint属性设置为kFluid,那么暗示编码器保持帧率。


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有